/**
 * -------------------------------------------------------------
 * 文件服务, 可直接上传至阿里OSS服务器
 * https://help.aliyun.com/product/31815.html
 * -------------------------------------------------------------
 * <form>
 * <img data-zupload="true" src="file://xxx.png"/>
 * <input type="file" data-zupload="true" name="pictureFile" /> <!-- 上传文件 -->
 * </form>
 * -------------------------------------------------------------
 * 配置data-zupload="true", 就会成为需要上传的文件, 只支持img和input type="file"
 * 如果配置data-zupload="xxx", 上传成功后的会根据xxx查找input来保存上传后的url; 如果找不到, 将会自动生成input[name=xxx]
 * -------------------------------------------------------------
 * $.fn.zupload(options);
 * -------------------------------
	@param options = {
		data: {
			currUsrId:string, // 当前用户工号, 必填
			systemId:string, // 系统ID(如tianxin), 必填, 最长10位
			domain:string, // 渠道号(如500), 必填, 最长3位
			businessType:Private|Public, 业务类型
			docType:string, // 单据类型, 最长10位
			docTypeNm:string, // 单据类型名称, 最长50位
			docId:string // 单据ID, 最长10位
		},
		process:function(e), 进度回调函数, e = { element:DOMElement, index:int, count:int, percent:int }
		step:function(e), 每个文件上传成功的回调函数, e = { element:DOMElement, index:int, count:int, url:string }
		done:function(e), 全部完成后的回调函数; 如果没有需要上传的内容,或不支持直接上传, 则直接回调; e = { finish:int, count:int }
		error:function(e), 失败回调函数; return true则失败后仍然继续上传; e = { element:DOMElement, index:int, count:int, message:string }
	}
 * count = 一共有几个文件需要上传
 * index = 当前上传第几个文件, 从0开始
 * finish = 完成了几个文件
 * percent = 当前文件上传进度, 0~100
 * step e.url = 文件上传成功后生成的url
 * error e.message = 失败原因
 * -------------------------------------------------------------
 * $.zfiles("query", options, succ, fail);
 * -------------------------------
	@param options = {
		data: {
			currUsrId:string, // 当前用户工号, 必填
			systemId:string, // 系统ID(如tianxin), 必填
			domain:string, // 渠道号(如500), 必填
			businessType:Private|Public, 业务类型
			queryMode:string, // 查询模式:0.按创建人查询|1.按单据查询
			createUserId:string, // 创建人查询条件
			docType:string, // 单据类型查询条件
			docIds:[string] // 单据ID列表
		},
		succ:function(list), // 成功回调函数
		fail:function(e) 失败回调函数; e = { message:string }
	}
	list[] = {
 		id: string, // 附件ID
		url: string, // URL
		remark: string // 备注
	}
 * -------------------------------------------------------------
 * $.zfiles("relate", options, succ, fail);
 * -------------------------------
	@param options = {
		data: {
			currUsrId:string, // 当前用户工号, 必填
			systemId:string, // 系统ID(如tianxin), 必填
			domain:string, // 渠道号(如500), 必填
			docType:string, // 单据类型查询条件
			docTypeNm:string, // 单据类型名称
			docId:string, // 单据ID查询条件
			ids:[string] // 附件ID列表
		},
		succ:function(), // 成功回调函数
		fail:function(e) 失败回调函数; e = { message:string }
	}
 * -------------------------------------------------------------
 * 需要在项目公共JS中配置的选项
 * -------------------------------
	$.zfiles.defaults = {
		url: {
			auth: "http://121.40.171.99:8080/cttq.com~portal~base~oss_web/AssumeRoleQry",
			query: "http://121.40.171.99:8080/cttq.com~portal~base~oss_web/AttachmentListQry",
			del: "http://121.40.171.99:8080/cttq.com~portal~base~oss_web/AttachmentDel",
			remark: "http://121.40.171.99:8080/cttq.com~portal~base~oss_web/AttachmentRemarkUpt"
		},
		auth: {
			tokenField: "credentials", // 通过服务器获取授权信息后如何获取token对象
			callbackField: "callback" // 服务器回调函数的字段名
		},
		upload: {
			idField: "data.id",
			urlField: "data.url",
			saveName: "${yyyy}/${MM}/${dd}/${HH}${mm}${ss}${SSS}${random-6}.${ext}"
		}
	};
 * saveName = 保存的文件路径, 支持以下自定义占位符 {yy} {yyyy} {MM} {dd} {HH} {mm} {ss} {SSS} {random-1}~{random-10}
 * -------------------------------------------------------------
 * 注意: 
 * 1. 需要用到OSS JS SDK
	https://help.aliyun.com/document_detail/32068.html
	https://github.com/ali-sdk/ali-oss/blob/master/dist/aliyun-oss-sdk.js
	<script src="assets/libs/aliyun/oss/aliyun-oss-sdk-4.6.2.js"></script>
 * 2. 如果auth接口有callback, 需要用到BASE64
	<script src="assets/libs/crypto-js/core.js"></script>
	<script src="assets/libs/crypto-js/enc-base64.js"></script>
 * 3. img跨域问题
	<img data-zupload="true" src="http://..." />, 会存在跨域的问题
	需要服务器允许且在img上加上crossOrigin才行, <img crossOrigin="*" src="..."/>
 * -------------------------------------------------------------
 * @author 赵卉华
 * date: 2016-12-15
 * -------------------------------------------------------------
 */
+function($) {

	var KEY = "__file_upload__";
	var INPUT = "data-zupload";
	$.fn.zupload = function(options) {

		return this.each(function() {
			var self = this;
			var $form = $(this);
			var o = $.extend(true, { }, $.zfiles.defaults, $form.zoptions(INPUT.replace(/^data-/, "")), options);
			var $files = o.find.call(self, o, INPUT);
			// 没有文件, 或者 不支持FormData(h5特性), 直接提交
			if ($files.length == 0 || !window.FormData) {
				$.isFunction(o.done) && o.done.call(self, { finish:0, count:$files.length });
				return;
			}
			// 获取授权, 然后开始上传
			auth(o, "Write", function(o) {
				upload.call(self, $files, o);
			});
		});
	};
	var checkResult = function(o, json, type, fail, callback) {
		var result = o.check(json, type);
		if (result !== true && result !== undefined) { // 失败
			if ($.dt.isString(result)) { result = { message:message }; }
			$.isFunction(fail) && fail(result);
		} else { // 成功
			callback();
		}
	}
	$.zfiles = function(type, options) {
		var o = $.extend(true, {}, $.zfiles.defaults, options);
		if (type == "query") {
			if (o.data.businessType == "Public") { // 公有读, 不需要签名
				$.zajax(o.query.url, o.data, false, function(json) { // json = { attachmentList:[ { } ], retCode:{ } }
					checkResult(o, json, type, o.fail, function() { // 成功
						var list = $.zhh.field(json, o.query.resultField);
						$.isFunction(o.succ) && o.succ(list);
					});
				});
			} else {
				// 获取授权, 然后开始查询
				auth(o, "Read", function(o) { // 通过服务器获取授权信息
					// 查询
					$.zajax(o.query.url, o.data, false, function(json) { // json = { attachmentList:[ { } ], retCode:{ } }
						checkResult(o, json, type, o.fail, function() { // 成功
							var list = $.zhh.field(json, o.query.resultField);
							// 逐一签名
							$.each(list || [], function() {
								this.url = o.client.signatureUrl(this.object, { expires:o.query.expires || 3600 });
							});
							$.isFunction(o.succ) && o.succ(list);
						});
					});
				});
			}
		} else if (type == "relate") {
			$.zajax(o.relate.url, o.data, false, function(json) { // json = { retCode:{ } }
				checkResult(o, json, type, o.fail, function() { // 成功
					$.isFunction(o.succ) && o.succ.call(form);
				});
			});
		} else {
			$.log.error("暂不支持操作类型: " + type);
		}
	};
	$.zfiles.defaults = {
		find : function(o, key) {
			return $(this).find("input[type=file],img").filter(function() {
				var input = $(this).attr(key);
				var matches = input != undefined && input != "false";
				if (matches && this.type == "file") {
					matches = this.files && this.files.length > 0;
				}
				return matches;
			});
		},
		data: {
			// currUsrId:string, // 当前用户工号, 必填
			// systemId:string, // 系统ID(如tianxin), 必填, 最长10位
			// domain:string, // 渠道号(如500), 必填, 最长3位
			// docType:string, // 单据类型, 最长10位
			// docTypeNm:string, // 单据类型名称, 最长50位
			// docId:string // 单据ID, 最长10位
		},
		auth: {
			// url: "http://121.40.171.99:8080/cttq.com~portal~base~oss_web/AssumeRoleQry",
			// tokenField: "credentials", // 通过服务器获取授权信息后如何获取token对象
			// callbackField: "callback" // 服务器回调函数的字段名
		},
		query: {
			// url: "http://121.40.171.99:8080/cttq.com~portal~base~oss_web/AttachmentListQry",
			// resultField: "attachmentList",
			// expires: 3600 // 授权时间
		},
		relate: {
			// url: "http://121.40.171.99:8080/cttq.com~portal~base~oss_web/AttachmentRelationUpt"
		},
		remark: {
			// url: "http://121.40.171.99:8080/cttq.com~portal~base~oss_web/AttachmentRemarkUpt"
		},
		upload: {
			// idField: "data.id",
			// urlField: "data.url",
			// saveName: "${yyyy}/${MM}/${dd}/${HH}${mm}${ss}${SSS}${random-6}.${ext}"
		},
		check: function(json, type) {
			// return true 表示成功, 不处理也表示成功
			// return message 表示失败, message=失败原因
			// return { code:string, message:string } 表示失败, code=错误返回码, message=失败原因
		},
		errorCode: {
			FileIsEmpty:"未选择文件",
			AccessDenied:"拒绝访问",
			BucketAlreadyExists:"Bucket已经存在",
			BucketNotEmpty:"Bucket不为空",  
			FileGroupTooLarge:"文件组过大",  
			FilePartStale:"文件Part过时",  
			InvalidArgument:"参数格式错误",  
			InvalidAccessKeyId:"Access ID不存在",  
			InvalidBucketName:"无效的Bucket名字",  
			InvalidObjectName:"无效的Object名字",  
			InvalidPart:"无效的Part",  
			InvalidPartOrder:"无效的part顺序",  
			InternalError:"OSS内部发生错误",  
			MissingContentLength:"缺少内容长度",  
			NoSuchBucket:"Bucket不存在",  
			NoSuchKey:"文件不存在",  
			NotImplemented:"无法处理的方法",  
			PreconditionFailed:"预处理错误",  
			RequestTimeTooSkewed:"发起请求的时间和服务器时间超出15分钟",  
			RequestTimeout:"请求超时",  
			SignatureDoesNotMatch:"签名错误",  
			TooManyBuckets:"用户的Bucket数目超过限制"
		}
	};
	var timeParts = ["yy", "yyyy", "MM", "dd", "HH", "mm", "ss", "SSS"];
	var getSaveAs = function(o, fileName) {
		var date = new Date();
		// 生成参数, 作为自定义占位符
		// 日期时间
		var params = {};
		for ( var i = 0; i < timeParts.length; i ++) {
			params[timeParts[i]] = Dates.format(date, timeParts[i]);
		}
		// 随机数1~10位
		var size = 10;
		var number = Randoms.number(size);
		for (var i = 1; i < size; i++) {
			params["random-" + i] = number.substring(0, i);
		}
		params["random-" + size] = number;
		var m = /\.(\w+)$/.exec(fileName);
		params["ext"] = m ? m[1] : "ukn"; // ukn = unknown
		return o.auth.token.dir.replace(/^(\/)/, "") + $.zhh.format(o.upload.saveName.replace(/\$(\{[-\w]+\})/g, "$1"), params);
	};
	var getCallbackHeader = function(o, fileName) {
		if (!o.auth.callback) { return undefined; }

		// 由于js sdk不支持自定义参数, 只好替换处理
		// 替换自定义参数: originalName, docId, docType, docTypeNm
		var params = $.extend(true, { originalName: fileName, updateUsrId: o.data.currUsrId }, o.data);
		// o.auth.callback = { callbackUrl:string, callbackHost:string, callbackBody:json, callbackBodyType:"application/json" }
		var cb = $.extend(true, {}, o.auth.callback);
		var bd = cb.callbackBody;
		for (var key in bd) {
			var value = bd[key];
			// https://help.aliyun.com/document_detail/31989.html
			// 用户自定义参数的Key一定要以x:开头
			var m = /\$?\{x\:([\-\w]+)\}/.exec(value);
			if (m) { bd[key] = params[m[1]]; }
		}
		// 阿里这规范, 简直是神一般的思路, { key:"${xxx}" } 这里不能有引号, 没有引号就不是规范的JSON了呀, zhaohuihua
		cb.callbackBody = JSON.stringify(bd).replace(/"(\$\{[-\w]+\})"/g, "$1");
		var C = CryptoJS;
		return C.enc.Base64.stringify(C.enc.Utf8.parse(JSON.stringify(cb)));
	};
	var auth = function(o, tokenType, callback) {
		// 根据url发送请求从服务器获取授权凭证
		var params = $.extend(true, { tokenType:tokenType }, o.data);
		// 通过服务器获取授权信息
		$.zajax(o.auth.url, params, false, function(json) { // json = { credentials:{ }, retCode:{ } }
			// 取服务器返回的token
			o.auth.token = $.zhh.field(json, o.auth.tokenField);
			o.auth.callback = $.zhh.field(json, o.auth.callbackField);
			var tk = o.auth.token;
			o.client = new OSS.Wrapper({
				region:tk.region, bucket:tk.bucket, 
				accessKeyId:tk.accessKeyId, accessKeySecret:tk.accessKeySecret, stsToken:tk.securityToken
			});
			$.isFunction(callback) && callback(o);
		});
	};
	var upload = function($files, o) {
		var form = this;
		var $form = $(this);
		// 上传文件
		$files.each(function(i) {
			var file = this;
			var $file = $(this);
			var e = { element:file, index:i, count:$files.length };
			// 未选中文件
			if (this.files && this.files.length == 0) {
				/*
				// 未选择文件则跳过
				$form.queue(KEY, function() {
					handleError.call(form, o, $.extend({}, e, {result:{ code:'FileIsEmpty' }}));
				});
				*/
				return true;
			}
			$form.queue(KEY, function() {
				var content;
				if ($file.is("img")) {
					// 将图片转换为blob, 加上type, 伪装成file对象
					content = imgToFile(file);
				} else {
					content = file.files[0];
				}
				var saveAs = getSaveAs(o, content.name);
				var callbackHeader = getCallbackHeader(o, content.name);
				o.client.multipartUpload(saveAs, content, {
					headers: { "x-oss-callback": callbackHeader },
					// 进度回调函数
					progress: function(percent) { // percent是0~1之间的小数
						return function (done) {
							// 汇报进度
							var p = (percent * 100).toFixed(0);
							$.isFunction(o.process) && o.process.call(form, $.extend({}, e, {percent:p}));
							done();
						};
					}
				}).then(function (json) {
					// result = true | string | { code:string, message:string }
					var result = o.check(json, "upload");
					if (result !== true && result !== undefined) { // 上传失败
						if ($.dt.isString(result)) { result = { message:message }; }
						handleError.call(form, o, $.extend(true, {}, e, { result:result } ));
					} else { // 上传成功
						var id = $.zhh.field(json, o.upload.idField);
						var url = $.zhh.field(json, o.upload.urlField);
						// 将文件URL填充到input中
						var $input = findOrCreateInput($form, $file);
						$input.val(url);
						$.isFunction(o.step) && o.step.call(form, $.extend({}, e, { id:id, url:url }));
						// 继续上传
						$form.dequeue(KEY);
					}
				}).catch(function (err) {
					handleError.call(form, o, $.extend(true, {}, e, { result: { code:err.code, message:err.message }}, { result:err } ));
				});
			});
		});
		// 全部完成后回调
		$form.promise(KEY).done(function() {
			$.isFunction(o.done) && o.done.call(self, { finish:$files.length, count:$files.length });
		});
		// 开始执行
		$form.dequeue(KEY);
	};
	var handleError = function(o, e) {
		var dequeue = true;
		if ($.isFunction(o.error)) {
			// e.result = string|json{code,message}
			if ($.isPlainObject(e.result)) {
				e.message = o.errorCode[e.result.code] || e.result.message;
			} else {
				e.message = e.result;
			}
			// return true则失败后仍然继续上传
			dequeue = o.error.call(this, e);
		}
		if (dequeue === true) { $(this).dequeue(KEY); }
	};
	var findOrCreateInput = function($form, $file) {
		var input = $file.attr(INPUT);
		// data-zupload="true", 只需上传, 不需要保存到input中
		// data-zupload="false", 在一开始就过滤掉了, 不会走到这里
		if (input == "true") { return $([]); }
		var $input;
		var i = $form.find("["+INPUT+"='"+ input +"']").index($file[0]);
		if (/\[i\]/.test(input)) { // data-zupload="image[i]"
			input = input.replace(/\[i\]/g, "["+i+"]"); // 把i替换成具体的序号
			$input = $form.find("input[name='"+ input +"']");
		} else { // data-zupload="image"
			$input = $form.find("input[name='"+ input +"']:eq("+i+")");
		}
		if ($input.length == 0) {
			$input = $('<input type="hidden" name="'+ input +'">').insertAfter($file);
		}
		return $input;
	};
	var imgToFile = function(img) {
		var base64Data = /^data:([\-\w]*\/[\-\w]*)(;charset=[\-\w]*)?(;base64)?,/i;
		var data;
		var mimeType = "image/png";
		var fileName = "image.png";
		if (base64Data.exec(img.src)) {
			data = img.src;
		} else {
			fileName = img.src.replace(/[\?#].*/, "");
			var index = fileName.lastIndexOf("/");
			if (index >= 0) { fileName = fileName.substring(index + 1); }
			var m = /\.(\w+)$/.exec(fileName);
			if (m) { mimeType = "image/" + m[1].replace(/jpg/, "jpeg"); }
			// 将图片画到画布上, 再转换为DataURL
			var canvas = document.createElement('canvas');
			canvas.width = img.naturalWidth;
			canvas.height = img.naturalHeight;
			canvas.style.display = "none";
			document.body.appendChild(canvas);
			canvas.getContext('2d').drawImage(img, 0, 0);

			// 这里, 会存在跨域的问题
			// 需要服务器允许且在img上加上crossOrigin才行, <img crossOrigin="*" src="..."/>
			data = canvas.toDataURL();
		}

		// DataURL转换为Blob
		// data = "data:image/png;base64,..."
		var bytes = window.atob(data.replace(base64Data, ''));
		var array = new Uint8Array(bytes.length);
		for (var i = 0; i < bytes.length; i++ ) {
			array[i] = bytes.charCodeAt(i);
		}

		var matcher = base64Data.exec(data);
		if (matcher) { 
			mimeType = matcher[1]; // 如mimeType=image/png
			fileName = fileName.replace(/\.[\-\w]+$/, "") + mimeType.replace(/(image\/)/, ".");
		}
		// 将图片转换为blob, 加上type和name, 伪装成file对象
		var blob = new Blob([array], { type:mimeType });
		blob.name = fileName;
		return blob;
	};
}(jQuery);

